﻿using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace Xcapp
{
    /// <summary>
    /// Main program class.
    /// </summary>
    internal class Program
    {
        private static int _count;
        private static int _maxRows = Console.WindowHeight - 2;

        private static OperationsHandler _supportedOperations;

        /// <summary>
        /// Mains the specified arguments.
        /// </summary>
        /// <param name="args">The arguments.</param>
        /// <returns></returns>
        internal static int Main(string[] args)
        {
            int result = 0;
            Lazy<IOperation, IOperationKey> operation = null;

            using (DirectoryCatalog dir = new DirectoryCatalog(".\\plugins", "*.dll"))
            using (CompositionContainer container = new CompositionContainer(dir))
            {
                _supportedOperations = new OperationsHandler();

                container.ComposeParts(_supportedOperations);
                // if "-?" or "/?" entered, show usage with plugin versions
                if (args.Length == 1 && args[0].Substring(1).Equals("?", StringComparison.OrdinalIgnoreCase))
                {
                    ShowUsage(true);
                }
                // if no plug ins found, let user know, no supported operations are availble
                else if (_supportedOperations.SupportedOperations.Count() == 0)
                {
                    Console.WriteLine("No supported operations found");
                }
                // if we have at least one arg, arg(s) are successfully parsed, and the operation requested (by key) is found, then proceed
                else if (args.Length > 0 && OperationArgs.Parse(args) && (operation = _supportedOperations.SupportedOperations.FirstOrDefault(o =>
                    o.Metadata.Key.Equals(OperationArgs.Key, StringComparison.OrdinalIgnoreCase))) != null)
                {
                    bool isFirstInstance = false;
                    string appName = Assembly.GetEntryAssembly().GetName().Name;
                    Mutex checkFirstInstance = new Mutex(false, "Global\\" + appName);

                    try
                    {
                        try
                        {
                            if (!OperationArgs.AllowMultipleInstances) { isFirstInstance = checkFirstInstance.WaitOne(3000); }
                        }
                        catch (AbandonedMutexException)
                        {
                            // continue operation, previous app probably crash or dind't exit cleanly
                        }

                        if (OperationArgs.AllowMultipleInstances || isFirstInstance)
                        {
                            bool operationResult = operation.Value.Execute();

                            Console.Write(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                            Console.Write("  -  application completed execution with status [");

                            if (operationResult)
                            {
                                Console.Write("Success");
                            }
                            else
                            {
                                Console.WriteLine("Failure");
                                result = 1;
                            }

                            Console.WriteLine("]");
                        }
                        else
                        {
                            Console.Write("There is already an instance of ");
                            Console.Write(appName);
                            Console.WriteLine(" running");
                        }
                    }
                    catch (Exception ex)
                    {
                        WriteLine(ex.ToString());
                        result = 1;
                    }
                    finally
                    {
                        if (isFirstInstance) { checkFirstInstance.ReleaseMutex(); }
                    }
                }
                else
                {
                    ShowUsage(false);
                }
            }

#if DEBUG
            Console.ReadLine();
#endif

            return result;
        }

        /// <summary>
        /// Shows the usage sample
        /// </summary>
        /// <param name="showExtendedHelp">if set to <c>true</c> [show extended help].</param>
        private static void ShowUsage(bool showExtendedHelp)
        {
            bool firstDesc = true;
            Assembly runner = Assembly.GetEntryAssembly();

            Write((AssemblyDescriptionAttribute.GetCustomAttribute(runner, typeof(AssemblyDescriptionAttribute)) as AssemblyDescriptionAttribute).Description);
            Write(" v");
            WriteLine(runner.GetName().Version.ToString());

            if (showExtendedHelp)
            {
                Assembly asm = Assembly.GetAssembly(typeof(IOperation));

                Write((AssemblyDescriptionAttribute.GetCustomAttribute(asm, typeof(AssemblyDescriptionAttribute)) as AssemblyDescriptionAttribute).Description);
                Write(" v");
                WriteLine(asm.GetName().Version.ToString());
            }

            WriteLine();
            Write("    Usage:  ");
            Write(runner.GetName().Name);
            WriteLine(" -arg1 -arg2 Arg2Value -arg3 -arg4 \"Arg,4,Values\" -arg5");
            WriteLine();

            // output operation and description
            foreach (var operation in _supportedOperations.SupportedOperations)
            {
                WriteLine();
                Write("\t\t-");
                Write(operation.Metadata.Key.ToLower());
                firstDesc = true;

                foreach (var description in operation.Value.Descriptions)
                {
                    if (!firstDesc)
                    {
                        Write("\t\t\t      ");
                    }
                    else
                    {
                        firstDesc = false;
                        Write("\t    ");
                    }

                    WriteLine(description);
                }
            }

            // output other argument info
            foreach (var arg in OperationArgs.ArgumentsDescriptions)
            {
                WriteLine();
                Write("\t\t-");
                Write(arg.Key.ToLower());
                firstDesc = true;

                foreach (var desc in arg.Descriptions)
                {
                    if (!firstDesc)
                    {
                        Write("\t\t\t      ");
                    }
                    else
                    {
                        firstDesc = false;
                        Write("\t    ");
                    }

                    WriteLine(desc);
                }
            }

            WriteLine();

            // show extended help
            if (showExtendedHelp)
            {
                WriteLine("Detected Plugins:");

                if (_supportedOperations.SupportedOperations.Count() > 0)
                {
                    foreach (var operation in _supportedOperations.SupportedOperations)
                    {
                        Assembly plugin = Assembly.GetAssembly(operation.Value.GetType());

                        Write("    ");
                        Write(plugin.GetName().Name);
                        Write(".dll v");
                        WriteLine(plugin.GetName().Version.ToString());
                    }
                }
                else
                {
                    WriteLine("    No valid plugins found");
                }
            }
            // let user know about "-?" option
            else
            {
                Write("\tFor extened info run \"");
                Write(runner.GetName().Name);
                WriteLine(" -?\"");
            }
        }

        /// <summary>
        /// Writes the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        private static void Write(string message)
        {
            if (_count > _maxRows)
            {
                _count = 0;
                Console.Write("Press any key to continue...");
                Console.ReadKey(true);
            }

            Console.Write(message);
        }

        /// <summary>
        /// Writes the line.
        /// </summary>
        /// <param name="message">The message.</param>
        private static void WriteLine(string message = "")
        {
            if (_count++ > _maxRows)
            {
                _count = 0;
                Console.Write("Press any key to continue...");
                Console.ReadKey(true);
            }

            if (_count == 1 && message == Environment.NewLine) { return; }

            Console.WriteLine(message);
        }
    }
}